//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-10-30     ZhangYihua   first version
//
// Description  : 
//################################################################################
//  
//           2^(-kp)
//            |            
//    in_dat  V               out_dat
// --------->(*)-(st)-[D]-(+)---------->
//      |    ______        ^      
//       -->|      |       |
// 2^(-ki)  |u_iacc|-------
// -------->|______| 
//           
// out_dat = in_dat*2^(-kp) + iacc; iacc = iacc + in_dat*2^(-ki);
module pi_ctrl #(    // proportional and integral accumulator 
parameter           IN_DW                   = 10,
parameter           IN_FW                   = 9,
parameter           OUT_FW                  = 24,   // must OUT_FW<=IN_FW+KI_MAX
parameter           PINC_DW                 = 12,   // must PINC_DW<=OUT_FW+IN_DW-IN_FW-KP_MIN
parameter           IINC_DW                 = 10,
parameter           IACC_FW                 = 27,   // must OUT_FW<=IACC_FW<=IN_FW+KI_MAX
parameter           KP_MAX                  = 15,
parameter           KP_MIN                  = 8,
parameter           KI_MAX                  = 23,   // must KI_MAX>=KP_MAX
parameter           KI_MIN                  = 12,

// the following parameters are calculated automatically
parameter           OUT_DW                  = MAX_F(IINC_DW, PINC_DW)+1,
parameter           KPW                     = $clog2(KP_MAX+1), // range [KP_MIN:KP_MAX]
parameter           KIW                     = $clog2(KI_MAX+1)  // range [KI_MIN:KI_MAX]
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input                                       in_vld,
input       signed  [IN_DW-1:0]             in_dat,     // s(  IN_DW,   IN_FW)
output  reg                                 out_vld,
output      signed  [OUT_DW-1:0]            out_dat,    // s( OUT_DW,  OUT_FW)

input               [KPW-1:0]               cfg_kp,     // range [KP_MIN:KP_MAX], gain = 2^(-kp)
input               [KIW-1:0]               cfg_ki,     // range [KI_MIN:KI_MAX], gain = 2^(-ki)

input                                       cfg_acc_clr,
input                                       cfg_acc_frz,
input       signed  [IINC_DW-1:0]           cfg_acc_ini,    // s(IINC_DW, IINC_FW), initial value
output      signed  [IINC_DW-1:0]           sts_acc_rtv     // s(IINC_DW, IINC_FW), real-time value
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          IN_IW                   = IN_DW-IN_FW;      // IN_IW<=0 is legal
localparam          IINC_FW                 = OUT_FW;
localparam          PINC_FW                 = OUT_FW;
localparam          IACC_DW                 = IACC_FW+IINC_DW-IINC_FW;

localparam          IN_EW                   = MAX_F(OUT_FW, IN_FW+KP_MAX)-IN_FW;
localparam          PGAIN_DW                = IN_DW+IN_EW-KP_MIN;

wire        signed  [PGAIN_DW-1:0]          pgain_dat;
wire        signed  [PINC_DW-1:0]           pinc_dat;
wire                                        pinc_over_nc;
wire        signed  [IINC_DW-1:0]           iinc_dat;

//################################################################################
// main
//################################################################################

s_gain_nkpo2 #(  // gain by negative k power of 2
        .DW                             (IN_DW+IN_EW                    ),	// data width
        .K_MAX                          (KP_MAX                         ),	// Maximum of cfg_k, K_MAX>=1
        .K_MIN                          (KP_MIN                         ) 	// Minimum of cfg_k
) u_gain_kp ( 
        .id                             ({in_dat, {IN_EW{1'b0}}}        ),
        .od                             (pgain_dat                      ),

        .cfg_k                          (cfg_kp                         )	// range [K_MIN:K_MAX], gain 
);

s_sat_tru_reg #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (IN_DW+IN_EW-KP_MIN             ),	// input data width
        .IFW                            (IN_FW+IN_EW                    ),	// input fractional width
        .ODW                            (PINC_DW                        ),	// output data width
        .OFW                            (PINC_FW                        ),	// output fractional width
        .TRU_MODE                       ("FLOOR"                        )	// discade fractional bits directly for less area and higher Fmax
) u_pinc_dat_tru ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .id                             (pgain_dat                      ),	// s(IDW, IFW), the MSB is sign
        .od                             (pinc_dat                       ),	// s(ODW, OFW), the MSB is sign
        .over                           (pinc_over_nc                   )
);

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        out_vld <=`U_DLY 1'b0;
    end else if (cke==1'b1) begin
        out_vld <=`U_DLY in_vld;
    end else
        ;
end

p_acc #(
        .IN_DW                          (IN_DW                          ),
        .IN_FW                          (IN_FW                          ),
        .OUT_DW                         (IINC_DW                        ),
        .OUT_FW                         (IINC_FW                        ),	// must OUT_FW<=IN_FW+K_MAX
        .ACC_FW                         (IACC_FW                        ),	// must OUT_DW<=ACC_DW<=IN_FW+K_MAX+(OUT_DW-OUT_FW)
        .K_MAX                          (KI_MAX                         ),
        .K_MIN                          (KI_MIN                         ) 
) u_iacc ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .in_vld                         (in_vld                         ),
        .in_dat                         (in_dat                         ),	// s( IN_DW,  IN_FW)
        .out_dat                        (iinc_dat                       ),	// s(OUT_DW, OUT_FW)

        .cfg_k                          (cfg_ki                         ),
        .cfg_acc_clr                    (cfg_acc_clr                    ),
        .cfg_acc_ini                    (cfg_acc_ini                    ),	// s(OUT_DW, OUT_FW)
        .cfg_acc_frz                    (cfg_acc_frz                    ),
        .sts_acc_rtv                    (sts_acc_rtv                    )	// s(IINC_DW, IINC_FW), real-time value
);

assign out_dat = pinc_dat + iinc_dat;

`include "func_param.v" // refer to MAX_F()

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

initial begin
    if (PINC_DW>OUT_FW+IN_IW-KP_MIN) begin
        $error("PINC_DW width is bigger than OUT_FW+IN_IW-KP_MIN.");
        $stop;
    end

    if ((OUT_FW>IACC_FW) || (IACC_FW>IN_FW+KI_MAX)) begin
        $error("IACC_FW is wrong.");
        $stop;
    end
end

// synopsys translate_on
`endif

endmodule
